require_relative 'custom_const'
require_relative 'util'

class Checker
  include CustomConst
  # chessmans    Array[Integer]
  # player    Integer
  #   0   white
  #   1   black
  attr_accessor :chessmans, :player

  def initialize(points = DEFAULT_POINTS, player = DEFAULT_PLAYER)
    @chessmans = [BLANK_CHESSMAN] * 100
    @player = player    # white
    load_points(points)
  end

  def load_points(points)
    points.each_with_index do |tmp_points, tmp_player|
      tmp_player |= CHESSMAN_FILTER
      tmp_points.each do |point|
        @chessmans[point] = tmp_player
      end
    end
  end

  # return Array[Integer] [1, 2, 3, 4]
  def adjoining_points(point)
    Util.soldier?(chessmans[point]) ? ap_soldier(point) : ap_king(point)
  end

  def ap_soldier(point)
    result = []
    move_flag = (-1) ** Util.player(chessmans[point])
    NEXT_INTERVAL.each do |val|
      tmp_point = point + val * move_flag
      result << tmp_point if Util.in_range?(tmp_point) && Util.adjacent_line?(tmp_point, point) && Util.blank?(chessman(tmp_point))
    end
    result
  end

  def ap_king(point)
    result = []
    NEXT_INTERVAL_ALL.each do |val|
      tmp_point = point
      while true
        npoint = tmp_point + val
        break unless Util.in_range?(npoint) && Util.adjacent_line?(tmp_point, npoint) && Util.blank?(chessman(npoint))

        result << npoint
        tmp_point = npoint
      end
    end
    result
  end

  # return Array[Integer, Integer, Array[Integer]]
  # [
  #   [next_point, len, [eat1, eat2, ...]],
  #   ...
  # ]
  # 能吃时必须吃，并且吃最多的棋子
  def edible_points(point)
    Util.soldier?(chessmans[point]) ? ep_soldier(point) : ep_king(point)
  end

  def ep_soldier(point, eats = [])
    result = []
    NEXT_INTERVAL_ALL.each do |val|
      point1, point2 = point + val, point + val * 2

      unless Util.in_range?(point1) && Util.in_range?(point2) && !eats.include?(point1) && \
        Util.adjacent_line?(point, point1) && Util.adjacent_line?(point1, point2) && \
        Util.present?(chessman(point1)) && Util.blank?(chessman(point2)) && !eql_player_for_point?(point1)
        next
      end

      tmp_eats = eats + [point1]
      tmp_rtn = ep_soldier(point2, tmp_eats)

      if tmp_rtn.empty?
        result << [point2, tmp_eats]
      else
        result.concat tmp_rtn
      end
    end

    if !result.empty?
      max_len = result.max_by{|_, eats| eats.size}.last.size
      result.select!{|_, eats| eats.size.eql?(max_len)}
    end

    result
  end

  def ep_king(point, eats = [])
    result = []
    NEXT_INTERVAL_ALL.each do |val|
      point1 = point
      is_continue = true
      while is_continue
        npoint = point1 + val
        if !Util.in_range?(npoint) || eats.include?(npoint) || !Util.adjacent_line?(point1, npoint)
          is_continue = false
          break
        end

        point1 = npoint
        if Util.present?(chessman(npoint))
          is_continue = !eql_player_for_point?(npoint)
          break
        end
      end
      next if !is_continue

      tmp_points = []
      point2 = point1
      while true
        npoint = point2 + val
        break unless Util.in_range?(npoint) && Util.blank?(chessman(npoint)) && Util.adjacent_line?(point2, npoint)

        point2 = npoint
        tmp_points << npoint
      end

      tmp_points.each do |point2|
        tmp_eats = eats + [point1]
        tmp_rtn = ep_king(point2, tmp_eats)
        if tmp_rtn.empty?
          result << [point2, tmp_eats]
        else
          result.concat tmp_rtn
        end
      end
    end

    if !result.empty?
      max_len = result.max_by{|_, eats| eats.size}.last.size
      result.select!{|_, eats| eats.size.eql?(max_len)}
    end

    result
  end

  def current_points
    result = []
    chessmans.each_with_index do |chessman, point|
      result << point if Util.present?(chessman) && eql_player_for_chessman?(chessman)
    end
    result
  end

  def eql_player_for_point?(point)
    eql_player_for_chessman?(chessmans[point])
  end

  def eql_player_for_chessman?(chessman)
    Util.player(chessman).eql?(player)
  end

  def kill(point)
    @chessmans[point] = BLANK_CHESSMAN
  end

  # return
  #   Array[Integer, Integer, Arrayer[Integer]]
  #   [[current, next, [eat1, eat2, eat3]]]
  #   [
  #     [0, 9, []],
  #     [1, 8, []], [1, 10, []]
  #   ]
  def possible_moves_not_eat
    current_points.reduce([]) do |result, point|
      tmp_result = adjoining_points(point)
        .map{|npoint| [point, npoint]}
      result.concat tmp_result
    end
  end

  # return
  #   Array[Integer, Integer, Arrayer[Integer]]
  #   [[current, next, [eat1, eat2, ...]]]
  #   [
  #     [0, 9, [1,2]],
  #     [1, 8, [5,3,4]], [1, 10, []]
  #   ]
  def possible_moves
    result = current_points.reduce([]) do |tmp, point|
      # byebug if point.eql?(31)
      tmp.concat edible_points(point).map{|npoint, eats| [point, npoint, eats]}
    end
    return possible_moves_not_eat if result.empty?

    max_len = result.max_by{|_, _, eats| eats.size}.last.size
    result.select!{|_, _, eats| eats.size.eql?(max_len)}

    result
  end

  def move(point, next_point, eats = [])
    @chessmans[next_point] = @chessmans[point]
    upgrade_king(next_point)
    update_player

    kill(point)
    eats.each {|eat| kill(eat)}
  end

  def upgrade_king(point)
    if Util.soldier?(@chessmans[point]) && (player.zero? && point >= 89) || (player.eql?(1) && point < 10)
      @chessmans[point] |= KING_FLAG
    end
  end

  def chessman(point)
    @chessmans[point]
  end

  def update_player
    @player = player.next % 2
  end

  def show
    puts "\nshow, current player: #{player}"

    puts '-----------------------------------------'
    LINE_LEN.times do |row|
      LINE_LEN.times do |col|
        point = row * LINE_LEN + col
        chessman = chessmans[point]

        if Util.blank?(chessman)
          val = '  '
        else
          val = Util.player(chessman).zero? ? 'O' : 'X'
          val << (Util.soldier?(chessman) ? ' ' : '+')
        end
        print "| #{val}"
      end
      puts "|\n-----------------------------------------"
    end
  end
end
